package asteroids.model;

import java.util.HashSet;
import java.util.Set;
import be.kuleuven.cs.som.annotate.*;
import asteroids.CollisionListener;
import asteroids.Util;
import asteroids.Vector;

/**
 * A class representing a World in the Asteroids game. A world is charactarised by a height and width.
 * Each world consists of a set of SpaceObjects.
 * 
 * @author Thijs Dieltjens - Wim Konings
 * |2BCwaElt
 * |https://asteroids-ogp.googlecode.com/svn/trunk/
 * @version 2.0
 * 
 * @invar 	Each world needs a proper set of spaceObjects 
 *        	| hasProperSetOfSpaceObjects()
 * @invar 	Each world has valid dimensions.
 * 		   	| isValidSize(this.getSize().getXCoordinate(), this.getSize().getYCoordinate())
 * 
 */

public class World {

	/**
	 * Creates a new World to play in.
	 * 
	 * @param 	height
	 * 			The maximum height this world can have.
	 * @param 	width
	 * 			The maximum width this world can have.
	 * @throws 	IllegalArgumentException
	 * 			The given height and width must be good dimensions.
	 * 			| isValidSize(height,width)
	 * @post	All the attributes of this world are equal to their corresponding parameter.
	 * 			| new.getSize() == new Vector(height, width) && new.getSpaceObjects() == spaceObjects
	 */
	public World(double height, double width) throws IllegalArgumentException{
		if(isValidSize(height,width)){
			this.size = new Vector(height, width);
			this.spaceObjects = new HashSet<SpaceObject>();			
		}
		else{
			throw new IllegalArgumentException();
		}
		assert hasProperSetOfSpaceObjects();
	}

	/**
	 * Returns the vector with the dimensions (length , width) of this world.
	 */
	@Immutable @Basic
	public Vector getSize(){
		return size;
	}

	/**
	 * @param 	height
	 * 			The given height.
	 * @param	width
	 * 			The given width.
	 * @return 	False if one of the given values is not a number, if the height is too high or less than zero or
	 * 			if the width is too high or less than zero, else true.
	 * 			|if( height.isNaN() || width.isNaN() || Util.fuzzyLessThanOrEqualTo(height,0) || Util.fuzzyLessThanOrEqualTo(MAX_HEIGHT, height)
	 * 			|		|| Util.fuzzyLessThanOrEqualTo(width,0) || Util.fuzzyLessThanOrEqualTo(MAX_WIDTH, width) )
	 * 			|	then result == false
	 * 			| else result == true		
	 */
	public static Boolean isValidSize(Double height,Double width){
		if(height.isNaN() || width.isNaN()){
			return false;
		}
		if( Util.fuzzyLessThanOrEqualTo(height,0) || Util.fuzzyLessThan(MAX_HEIGHT, height)){
			return false;
		}
		if( Util.fuzzyLessThanOrEqualTo(width,0) || Util.fuzzyLessThan(MAX_WIDTH, width)){
			return false;
		}
		return true;
	}

	/**
	 * Checks if a space object is in this world's boundaries
	 * 
	 * @param 	coordinates
	 * 			The vector of coordinates of a space object.
	 * @param   radius
	 * 			The radius of this spaceObject.
	 * @return	True if and only if the coordinates is between the upper-under boundary and the left-right boundary.
	 * 			| result == ( (getSize().getXCoordinate() >= (coordinates.getXCoordinate()+radius)) && (coordinates.getXCoordinate()-radius >= 0) &&
	 * 			|				(getSize().getYCoordinate() >= (coordinates.getYCoordinate()+radius)) && (coordinates.getYCoordinate()-radius >= 0) )
	 */
	public boolean isInWorldBoundaries(Vector coordinates, double radius){
		if(Util.fuzzyLessThan(getSize().getXCoordinate(),coordinates.getXCoordinate()+radius) ||
				Util.fuzzyLessThan(coordinates.getXCoordinate()-radius,0.0000)){
			return false;
		}
		if(Util.fuzzyLessThan(getSize().getYCoordinate(),coordinates.getYCoordinate()+radius)||
				Util.fuzzyLessThan(coordinates.getYCoordinate()-radius,0.0000)){
			return false;
		}
		return true;
	}

	/**
	 * Checks whether a space object is in the boundaries of this world or not.
	 * 
	 * @param 	spaceObject
	 * 			The space object to check if it's in the boundaries of this world.
	 * @return	True if and only if the space object is in the boundaries of this world.
	 * 			| result == isInWorldBoundaries(spaceObject.getCoordinates(),spaceObject.getRadius())
	 */
	public boolean isInWorldBoundaries(SpaceObject spaceObject){
		return isInWorldBoundaries(spaceObject.getCoordinates(), spaceObject.getRadius());		
	}

	private Vector size;

	/**
	 * Returns the maximum width of this world.
	 * 
	 * @return	The maximum width of this world.
	 * 			| result == Double.MAX_VALUE
	 */
	public final static double MAX_WIDTH = Double.MAX_VALUE;

	/**
	 * Returns the maximum height of this world.
	 * 
	 * @return	The maximum height of this world.
	 * 			| result == Double.MAX_VALUE
	 */
	public final static double MAX_HEIGHT = Double.MAX_VALUE;

	/**
	 * Returns the list of space objects in this world.
	 */
	@Basic
	public Set<SpaceObject> getAllSpaceObjects(){
		return spaceObjects;
	}

	/**
	 * Returns the set of ships in this world.
	 * 
	 * @return 	All the ships in this world of the given subclass of SpaceObject className.
	 * 			| for each space object in getSpaceObjects:
	 * 			|		if(spaceObject.getClass() == className)
	 * 			|			then result.contains(spaceObject)
	 */
	@SuppressWarnings("unchecked")
	public <T extends SpaceObject> Set<T> getSpaceObjectsOfType(Class<T> className){
		Set<T> result = new HashSet<T>();
		for(SpaceObject spaceObject: this.getAllSpaceObjects()){
			if(spaceObject.getClass()==className){
				result.add((T) spaceObject);
			}
		}
		return result;	
	}

	/**
	 * @param	spaceObject
	 * 			The space object that needs to be checked if it is in this world.
	 * @return 	True if the space object can be added to this world, is in the spaceObjects set of this class 
	 * 			and has a reference to this World.
	 * 			| result == ( canHaveAsSpaceObject(spaceObject) && getAllSpaceObjects().contains(spaceObject) 
	 * 			|					&& spaceObject.getWorld()==this )
	 * @throws 	NullPointerException
	 *		  	The given spaceObject is null
	 *			| (spaceObject==null)
	 */
	public boolean isValidSpaceObjectInThisWorld(SpaceObject spaceObject) throws NullPointerException{
		if(spaceObject!=null){
			return (canHaveAsSpaceObject(spaceObject) && getAllSpaceObjects().contains(spaceObject) && spaceObject.getWorld()==this );
		}
		else{
			throw new NullPointerException();
		}
	}

	/**
	 * Determines whether the given spaceObject form a pair of a bullet and its corresponding source ship.
	 * 
	 * @param 	so1
	 * 			The first spaceObject.
	 * @param 	so2
	 * 			The second spaceObject.
	 * @return 	True if so2 is the sourceShip of the bullet so1 or it's the other way around.
	 * 			| result==( (so1 instanceof Bullet && so2 instanceof Ship && (((Bullet) so1).getSource() == (Ship) so2)) ||
	 * 			| 			(so2 instanceof Bullet && so1 instanceof Ship &&(((Bullet) so2).getSource() == (Ship) so1)) )
	 */
	public static boolean isBulletSourceShipPair(SpaceObject so1, SpaceObject so2){
		if(so1 instanceof Bullet && so2 instanceof Ship && (((Bullet) so1).getSource() == (Ship) so2)){
			return true;
		}
		if(so2 instanceof Bullet && so1 instanceof Ship &&(((Bullet) so2).getSource() == (Ship) so1)){
			return true;
		}
		return false;
	}

	/**
	 * Checks if the space objects in the space objects set of this world are valid.
	 * 
	 * @return  True if and only if all the space objects in the space objects set of this world are valid.
	 * 			|  for each spaceObject getAllSpaceObjects():
	 * 			|  		isValidSpaceObjectInThisWorld(spaceObject)
	 */
	public boolean hasProperSetOfSpaceObjects(){
		for(SpaceObject spaceObject : getAllSpaceObjects()){
			if(!isValidSpaceObjectInThisWorld(spaceObject))			
				return false;
		}
		return true;
	}

	/**
	 * Checks whether the given space object can be added to the set of this world's space objects.
	 * 
	 * @param 	spaceObject
	 * 			The space object that needs to be checked.
	 * @return 	False if the space object is null, isn't in this world's boundaries or collides with another space object
	 * 			or if it's a bullet, and the source of the bullet has 3 or more bullets remaining in this world.
	 * 			| result = ! ( spaceObject==null || !isInWorldBoundaries(spaceObject) || !hasNoInvalidCollision(spaceObject) )
	 * 			| 				|| ( spaceObject instanceof Bullet && getNbBulletsFromShip(((Bullet) spaceObject).getSource()) >= 3 )
	 */
	public boolean canHaveAsSpaceObject(SpaceObject spaceObject){
		if(spaceObject==null || !isInWorldBoundaries(spaceObject) || !hasNoInvalidCollision(spaceObject)) {
			return false;
		}
		if(spaceObject instanceof Bullet && getNbBulletsFromShip(((Bullet) spaceObject).getSource()) >= 3){
			return false;
		}
		return true;
	}

	/**
	 * Checks whether this world contains the given space object.
	 * 
	 * @param 	so
	 * 			The space object that needs to be checked.
	 * @return 	True if this world contains the space object.
	 * 			| result = getAllSpaceObjects().contains(so)
	 */
	public boolean hasAsSpaceObject(SpaceObject so) {
		return getAllSpaceObjects().contains(so);
	}

	/**
	 * Adds the given space object to the list of space objects of this world.
	 * 
	 * @param 	spaceObject
	 * 			The space object to be added.
	 * @post 	If this world can have this space object and it is not already in this world, it is added to this world.
	 * 			| if(canHaveAsSpaceObject(spaceObject) && !getAllSpaceObjects().contains(spaceObject)) then
	 * 			|	(new SpaceObject).getWorld()==this && new.getAllSpaceObjects.contains(spaceObject)
	 * @effect	If the space object is a bullet which is in the boundaries, but collides with a space object and also hasn't got a 
	 * 			source ship which already has already 3 or more bullets in this world, the bullet and the object with which it collides are terminated. 
	 * 			| if(spaceObject!=null && (spaceObject instanceof Bullet) && (getNbBulletsFromShip(((Bullet) spaceObject).getSource())<3) 
	 *			|	 && isInWorldBoundaries(spaceObject) && !hasNoInvalidCollision(spaceObject) && getCollidingObject(spaceObject)!=null)
 	 *			| then 
	 *			| 	getCollidingObject(spaceObject).terminate() && spaceObject.terminate()
	 * @throws 	IllegalArgumentException
	 *			| The given space object can not be added.
	 *			| !(canHaveAsSpaceObject(spaceObject) && !getAllSpaceObjects().contains(spaceObject)) 
	 *			|	 && !(spaceObject!=null && (spaceObject instanceof Bullet) && (getNbBulletsFromShip(((Bullet) spaceObject).getSource())<3) 
	 *			|	 && isInWorldBoundaries(spaceObject) && !hasNoInvalidCollision(spaceObject) && getCollidingObject(spaceObject)!=null)
	 */
	public void addSpaceObject(SpaceObject spaceObject) throws IllegalArgumentException{
		if(canHaveAsSpaceObject(spaceObject) && !getAllSpaceObjects().contains(spaceObject)){
			spaceObject.setWorld(this);
			getAllSpaceObjects().add(spaceObject);
		}
		else if(spaceObject!=null && (spaceObject instanceof Bullet) && (getNbBulletsFromShip(((Bullet) spaceObject).getSource())<3) 
				&& isInWorldBoundaries(spaceObject) && !hasNoInvalidCollision(spaceObject) && getCollidingObject(spaceObject)!=null) {
					getCollidingObject(spaceObject).terminate();
					spaceObject.terminate();				
			}
		else{
			throw new IllegalArgumentException();
		}

	}
	/**
	 * Removes a given space object out of this world.
	 * 
	 * @param 	spaceObject
	 * 			The space object to remove out of this world.
	 * @throws	NullPointerException
	 * 			The given spaceObject is null or set of spaceObjects is null.
	 * 			|spaceObject==null || this.getAllSpaceObjects()==null
	 * @post	The set of spaceObjects of this world no longer contains the given spaceObject.
	 * 			| !new.getSpaceObject().contains(spaceObject)
	 * @post	The spaceObject's world is set to null.
	 * 			| (new spaceObject).getWorld()==null
	 */
	public void removeSpaceObject(SpaceObject spaceObject) throws NullPointerException{
		if(spaceObject!=null && spaceObjects!=null){
			spaceObject.setWorld(null);
			spaceObjects.remove(spaceObject);			
		}
		else{
			throw new NullPointerException();
		}
	}

	private Set<SpaceObject> spaceObjects;

	/**
	 * Makes two Space objects bounce of each other.
	 * 
	 * @param 	spaceObject
	 * 			The first space object which is a potential bouncer. 
	 * @param 	spaceObject2
	 * 			The second space object which is a potential bouncer. 
	 * @post	We will declare velocPosDotProd as a dotProduct of the difference in position with the difference in velocities.
	 * 			| (spaceObject.getVelocities().subtractVector(spaceObject2.getVelocities())).dotProduct(spaceObject.getCoordinates().subtractVector(spaceObject2.getCoordinates()))
	 * @post	The first space object bounced of the other one and its velocities changes.
	 * 			| spaceObject.setVelocities( 
	 * 			|	spaceObject.getVelocities().getXCoordinate() 
	 * 			|		- (2*spaceObject.getMass()*spaceObject2.getMass()*veloPosDotProd/((spaceObject.getRadius() + spaceObject2.getRadius())*(spaceObject.getMass()+spaceObject2.getMass())))
	 * 			|		* (spaceObject.getCoordinates().getXCoordinate()-spaceObject2.getCoordinates().getXCoordinate())/(spaceObject.getRadius() + spaceObject2.getRadius())/spaceObject.getMass() ,
	 * 			| 	spaceObject.getVelocities().getYCoordinate() 
	 * 			|		- (2*spaceObject.getMass()*spaceObject2.getMass()*veloPosDotProd/((spaceObject.getRadius() + spaceObject2.getRadius())*(spaceObject.getMass()+spaceObject2.getMass())))
	 * 			|		* (spaceObject.getCoordinates().getYCoordinate()-spaceObject2.getCoordinates().getYCoordinate())/(spaceObject.getRadius() + spaceObject2.getRadius())/spaceObject.getMass()) )
	 * @post 	The second space object bounced of the other one and its velocities changes.
	 * 			| spaceObject.setVelocities( 
	 * 			|	spaceObject2.getVelocities().getXCoordinate() 
	 * 			|		- (2*spaceObject.getMass()*spaceObject2.getMass()*veloPosDotProd/((spaceObject.getRadius() + spaceObject2.getRadius())*(spaceObject.getMass()+spaceObject2.getMass())))
	 * 			|		* (spaceObject.getCoordinates().getXCoordinate()-spaceObject2.getCoordinates().getXCoordinate())/(spaceObject.getRadius() + spaceObject2.getRadius())/spaceObject2.getMass() ,
	 * 			| 	spaceObject2.getVelocities().getYCoordinate() 
	 * 			|		- (2*spaceObject.getMass()*spaceObject2.getMass()*veloPosDotProd/((spaceObject.getRadius() + spaceObject2.getRadius())*(spaceObject.getMass()+spaceObject2.getMass())))
	 * 			|		* (spaceObject.getCoordinates().getYCoordinate()-spaceObject2.getCoordinates().getYCoordinate())/(spaceObject.getRadius() + spaceObject2.getRadius())/spaceObject2.getMass()) )
	 * @throws 	IllegalArgumentException
	 *			One of the space objects is not part of this world or both of the space objects are the same.
	 *			| (!hasAsSpaceObject(spaceObject2) || !hasAsSpaceObject(spaceObject) || spaceObject==spaceObject2)
	 *
	 */
	public void bounce(SpaceObject spaceObject,SpaceObject spaceObject2) throws IllegalArgumentException{
		if(!hasAsSpaceObject(spaceObject2) || !hasAsSpaceObject(spaceObject) || spaceObject==spaceObject2){
			throw new IllegalArgumentException();
		}
		Vector diffPos = spaceObject.getCoordinates().subtractVector(spaceObject2.getCoordinates());
		Vector diffVelo = spaceObject.getVelocities().subtractVector(spaceObject2.getVelocities());
		double veloPosDotProd = diffVelo.dotProduct(diffPos);
		double sigma = spaceObject.getRadius() + spaceObject2.getRadius();			
		double temp = 2*spaceObject.getMass()*spaceObject2.getMass()*veloPosDotProd/(sigma*(spaceObject.getMass()+spaceObject2.getMass()));
		double Jx = temp*(diffPos.getXCoordinate())/sigma;
		double Jy = temp*(diffPos.getYCoordinate())/sigma;

		Vector newVelocityFirstSpaceObject = new Vector(spaceObject.getVelocities().getXCoordinate() - Jx/spaceObject.getMass(),
				spaceObject.getVelocities().getYCoordinate() - Jy/spaceObject.getMass());
		Vector newVelocitySecondSpaceObject = new Vector(spaceObject2.getVelocities().getXCoordinate() + Jx/spaceObject2.getMass(),
				spaceObject2.getVelocities().getYCoordinate() + Jy/spaceObject2.getMass());

		spaceObject.setVelocities(newVelocityFirstSpaceObject);
		spaceObject2.setVelocities(newVelocitySecondSpaceObject);
	}
	
	/**
	 * Returns the space object which collides with the given space object.
	 * 
	 * @param 	so
	 * 		  	The given space object. 
	 * @return 	If the result is not null, the object which collides with the given space object is returned.
	 * 			| if(result!=null) 
	 * 			| 	then result.overlap(so) && result!=so && !isBulletSourceShipPair(result,so)
	 */
	public SpaceObject getCollidingObject(SpaceObject so){
		for(SpaceObject so2 : getAllSpaceObjects()){
			if(so2.overlap(so) && so2!=so && !isBulletSourceShipPair(so, so2)){
				return so2;
			}
		}
		return null;		
	}
	
	/**
	 * Checks if a space object with given radius and position in this world is in collision with another space object or not.
	 * 
	 * @param	radius
	 * 			The radius of the space object.
	 * @param	position
	 * 			The vector of position of the space object. 
	 * @return	True if and only if this space object doesn't collide with another space object in this world.
	 * 			|	for each spaceObject getAllSpaceObjects():
	 * 			|	result == !((spaceObject.overlap(newSpaceObject) && !isBulletSourceShipPair(spaceObject, newSpaceObject)
	 *			|					&& !(spaceObject instanceof Bullet || newSpaceObject instanceof Bullet)
	 *			|					&& spaceObject!=newSpaceObject) )
	 */
	public boolean hasNoInvalidCollision(SpaceObject newSpaceObject) throws NullPointerException{
		if(newSpaceObject == null) {
			throw new NullPointerException();
		}
		if(getAllSpaceObjects()!=null){
			for(SpaceObject spaceObject : getAllSpaceObjects()){
				if(spaceObject.overlap(newSpaceObject) && !isBulletSourceShipPair(spaceObject, newSpaceObject)
						&& spaceObject!=newSpaceObject){
					return false;
				}
			}
		}
		return true;
	}

	/**
	 * Resolves the collision with a space object and another space object (or boundary).
	 * 
	 * @param 	collision
	 * 			The given collision with a space object and another object (or boundary).
	 * @param 	cl
	 * 			The given CollisionListener.
	 * @effect  Checks whether it's a boundary collision or an object collision and executes the methods needed 
	 * 			to resolve those collisions.
	 * 			| if(collision.getSecondSpaceObject()==null
	 * 			|	then resolveBoundaryCollision(collision,cl)
	 * 			| else
	 * 			|	resolveSpaceObjectCollision(collision, cl)
	 */
	@Model
	private void resolve(Collision collision, CollisionListener cl) {	
		//boundaries
		if(collision.getSecondSpaceObject()==null){			
			resolveBoundaryCollision(collision, cl);
		}
		//spaceObjects
		else{
			resolveSpaceObjectCollision(collision, cl);
		}
	}

	/**
	 * Resolves the collision with a space object and another space object.
	 * 
	 * @param 	collision
	 * 			The Collision that needs to be resolved.
	 * @param 	cl
	 * 			The CollisionListener that will take care of video- and audio effects for the collision.
	 * @effect 	If one of the spaceObjects of the collision is a bullet, terminate both and tell cl a collision had been made.
	 * 			| if( (collision.getSpaceObject() instanceof Bullet) ||
	 * 			|		(collision.getSecondSpaceObjec() instanceof Bullet) )
	 * 			| then
	 * 			| 	cl.objectCollision(collision.getSpaceObject(),collision.getSecondSpaceObject(),
	 *  		|		collision.getPosition().getXCoordinate(), collision.getPosition().getYCoordinate())					
	 *			|	&&	collision.getSpaceObject().terminate();
	 *			|	&&	collision.getSecondSpaceObject.terminate();
	 * @effect 	If the first spaceObject is an Asteroid and the second is a Ship, terminate the second and notify collisionListener of this collision.
	 *			| if( (collision.getSpaceObject() instanceof Asteroid &&)
	 * 			|		(collision.getSecondSpaceObjec() instanceof Ship) )
	 * 			| then
	 * 			| 	cl.objectCollision(collision.getSpaceObject(),collision.getSecondSpaceObject(),
	 *  		|		collision.getPosition().getXCoordinate(), 	collision.getPosition().getYCoordinate())					
	 *			|	&&	collision.getSecondSpaceObject.terminate()
	 * @effect	If the first spaceObject is a ship and the second is an Asteroid, terminate the first and notify collisionListener of this collision.
	 *			| if( (collision.getSpaceObject() instanceof Ship &&)
	 * 			|		(collision.getSecondSpaceObjec() instanceof Asteroid) )
	 * 			| then
	 * 			| 	cl.objectCollision(collision.getSpaceObject(),collision.getSecondSpaceObject(),
	 *  		|		collision.getPosition().getXCoordinate(), 	collision.getPosition().getYCoordinate())					
	 *			|	&&	collision.getSpaceObject.terminate()
	 * @effect	If one of the spaceObjects involved in the collision is not a Bullet AND they are not a 
	 *			combination of Asteroid and a Ship,	make the two spaceObjects bounce of each other.
	 *			|if( !((collision.getSpaceObject() instanceof Bullet) || collision.getSecondSpaceObject() instanceof Bullet) &&
	 *			|	 !((collision.getSpaceObject() instanceof Asteroid) && (collision.getSecondSpaceObject() instanceof Ship)) &&
	 *			|    !((collision.getSecondSpaceObject() instanceof Asteroid) && (collision.getSpaceObject() instanceof Ship)) )
	 *			|then bounce(collision.getSpaceObject(), collision.getSecondSpaceObject())
	 * @throws	IllegalArgumentException
	 *			The given collision is null.
	 *			| collision == null
	 */
	@Model
	private void resolveSpaceObjectCollision(Collision collision, CollisionListener cl) throws IllegalArgumentException{
		if(collision ==null){
			throw new IllegalArgumentException();
		}
		SpaceObject so = collision.getFirstSpaceObject();
		SpaceObject so2 = collision.getSecondSpaceObject();

		if(so instanceof Bullet || so2 instanceof Bullet){
			cl.objectCollision(so,so2, collision.getPosition().getXCoordinate(), 
					collision.getPosition().getYCoordinate());					
			so.terminate();
			so2.terminate();
		}
		else if(so instanceof Asteroid && so2 instanceof Ship){
			cl.objectCollision(so,so2, collision.getPosition().getXCoordinate(), 
					collision.getPosition().getYCoordinate());					
			so2.terminate();
		}
		else if(so2 instanceof Asteroid && so instanceof Ship){
			cl.objectCollision(so,so2, collision.getPosition().getXCoordinate(), 
					collision.getPosition().getYCoordinate());					
			so.terminate();
		}
		else if(so.getClass() == so2.getClass()){							
			bounce(so,so2);
		}
	}

	/**
	 * Resolves the collision with a space object and a boundary.
	 * 
	 * @param 	collision
	 * 			The given collision with a boundary that needs to be resolved.
	 * @param 	cl
	 * 			The collisionListener that will listen the collision.
	 * @pre		The collision is one of a spaceObject with a boundary.
	 * 			| collision.getSecondSpaceObject()==null
	 * @pre 	The space object in the collision is in this world.
	 * 			| hasAsSpaceObject(collision.getSpaceObject())
	 * @post 	The spaceObject involved in the collision bounces of the boundary it collides with.	
	 *  		| if( Util.fuzzyEquals(collision.getPosition().getXCoordinate(),getSize().getXCoordinate()) 
	 *			|		|| Util.fuzzyEquals(collision.getPosition().getXCoordinate(),0) )
	 *			|	then (new collision.getSpaceObject()).getSpaceObject().getVelocities().getXCoordinate() ==
	 *			|				- collision.getSpaceObject()).getSpaceObject().getVelocities().getXCoordinate())
	 *			| else 	(new collision.getSpaceObject()).getSpaceObject().getVelocities().getYCoordinate() ==
	 *			|				- collision.getSpaceObject()).getSpaceObject().getVelocities().getYCoordinate())
	 * @effect 	cl will handle the given boundaryCollision.
	 * 			|cl.boundaryCollision(collision.getSpaceObject(), collision.getPosition().getXCoordinate(), 
	 *			|						collision.getPosition().getYCoordinate())	
	 * @effect 	If the spaceObject involved in the given collision is a bullet, either terminate it or make it bounce
	 * 			depending of the status of the spaceObject.
	 * 			| if(collision.getSpaceObject() instanceof Bullet)
	 * 		    |	then ((Bullet) collision.getSpaceObject()).terminateBoundary()
	 */
	@Model
	private void resolveBoundaryCollision(Collision collision, CollisionListener cl){
		assert(collision.getSecondSpaceObject()==null);
		assert(hasAsSpaceObject(collision.getFirstSpaceObject()));
		Vector velocity = collision.getFirstSpaceObject().getVelocities();
		Vector newVelocity;
		SpaceObject so = collision.getFirstSpaceObject();
		cl.boundaryCollision(collision.getFirstSpaceObject(), collision.getPosition().getXCoordinate(), collision.getPosition().getYCoordinate());
		//botst met x
		if(Util.fuzzyEquals(collision.getPosition().getXCoordinate(),getSize().getXCoordinate()) 
				|| Util.fuzzyEquals(collision.getPosition().getXCoordinate(),0)){
			newVelocity = new Vector(-velocity.getXCoordinate(),velocity.getYCoordinate());
		}
		//botst met y
		else{
			newVelocity = new Vector(velocity.getXCoordinate(),-velocity.getYCoordinate());		
		}
		collision.getFirstSpaceObject().setVelocities(newVelocity);	
		if(so instanceof Bullet){
			((Bullet) so).terminateBoundary();
		}			
	}

	/**
	 * Returns the first collision that will happen in this world.
	 * 
	 * @param 	time 
	 * 			The upper time boundary in which the collision must happen.
	 * @return  The first Collision that will happen in this world in less than the given time.
	 * 			| if (result!=null)
	 * 			| 	then
	 * 			| 	for each spaceObject in getSpaceObjects:
	 * 			|	 	for each secondSpaceObject in getSpaceObjects:
	 * 			|	 		   ( spaceObject.getCollisionPosition(secondSpaceObject) == null || isBulletSourceShipPair(so, so2) )  
	 * 			| 			|| ( ((spaceObject.getTimeCollision(secondSpaceObject)>= result.getTime()) && (spaceObject.getTimeCollision(secondSpaceObject)<= time) ) )
	 * 			| 			|| ( ((spaceObject.getTimeCollisionBoundary()>=result.getTime()) && (spaceObject.getTimeCollisionBoundary()<=time)) )
	 * @return  Null if the first collision that will happen is after the given time, or no collision occurs at all.
	 * 			| if(result==null)
	 * 			|	then
	 * 			| 	for each spaceObject in getSpaceObjects:
	 * 			|	 	for each secondSpaceObject in getSpaceObjects:
	 * 			|	 		( (spaceObject.getTimeCollision(secondSpaceObject)>time && spaceObject.getTimeCollision()>time) 
	 * 			|	 			|| getSpaceObjects.isEmpty() )
	 */
	public Collision getFirstCollision(double time){
		double shortestTime=time;
		SpaceObject agent1=null;
		SpaceObject agent2=null;
		Vector position=null;

		for(SpaceObject so : getAllSpaceObjects()){
			for(SpaceObject so2: getAllSpaceObjects()){
				Double timeToSpaceObject = so.getTimeToCollision(so2);
				if(isBulletSourceShipPair(so, so2)){
					//DO NOTHING if the spaceObjects are a bullet with its corresponding source (Ship).
				}
				else{
					//other SpaceObject
					if(Util.fuzzyLessThan(timeToSpaceObject,shortestTime) && so2!=agent1 && !timeToSpaceObject.isInfinite() 
							&& Util.fuzzyLessThanOrEqualTo(0,timeToSpaceObject)) {
						shortestTime = timeToSpaceObject;
						agent1 = so; 
						agent2 = so2;
						position = agent1.getCollisionPosition(agent2);		
					}
					Double timeToBoundary = so.getTimeToCollisionBoundary();
					// boundary
					if(Util.fuzzyLessThan(timeToBoundary,shortestTime) && !timeToBoundary.isInfinite()
							&& Util.fuzzyLessThanOrEqualTo(0, timeToBoundary))	{
						shortestTime = timeToBoundary;
						agent1=so;	
						agent2=null;
						position = agent1.getCollisionPositionBoundary();
					}		
				}
			}
		}
		if(position == null){
			return null;
		}
		Collision collision = new Collision(shortestTime, position, agent1, agent2);
		return collision;
	}

	/**
	 * This method moves all the space object during a given increment of time.
	 * 
	 * @param 	dt
	 * 			A given increment of time
	 * @effect	The space objects will be moved during a given increment of time.
	 * 			| for each spaceObject of getAllSpaceObjects():
	 * 			| 	spaceObject.move(dt)
	 * @effect	All the ships will be accelerated (if thruster is active => see move) for the given time increment.
	 * 			| for each spaceObject of getAllSpaceObjects():
	 * 			| 	if(spaceObject instanceOf Ship)
	 * 			| 		then ((Ship) spaceObject).thrust(dt)
	 */
	@Raw @Model
	private void moveAllSpaceObjects(double dt){
		for(SpaceObject spaceObject : getAllSpaceObjects()){
			if(spaceObject instanceof Ship){
				((Ship) spaceObject).thrust(dt);
			}
			spaceObject.move(dt);			
		}	
	}

	/**
	 * Evolves this world during a given time increment.
	 * 
	 * @param 	dt
	 * 			The given time increment.
	 * @param	cl
	 * 			The given collision listener.
	 * @effect	If there is no collision or the the time increment is smaller than the time to collision, all space objects will be advanced
	 * 			and all programs will be executed during a given time increment.
	 * 			Else all the spaceObjects will be advanced with the time to the collision, the collision will be resolve and all programs
	 * 			will be executed during the time to collision.
	 * 			| if(collision==null || Util.fuzzyLessThan(dt, collision.getTime()))
	 * 			| 	then 
	 * 			|		moveAllSpaceObjects(dt)
	 * 			|		executeAllPrograms(dt)
	 *			| else
	 * 			|	moveAllSpaceObjects(this.getFirstCollision(dt).getTime())
	 * 			|	executeAllPrograms(this.getFirstCollision(dt).getTime())
	 * 			| 	resolve(this.getFirstCollision(dt),cl)
	 * @effect	If getFirstcollision gives a collision that happens before the given time dt, this method will be 
	 * 			used again with the time parameter equal to the difference of the given time and the time of the collision.
	 * 			| if(this.getFirstCollision(dt)!=null) &&(Util.fuzzyLessThan(0,this.getFirstCollision(dt).getTime()))
	 * 			| 	then evolve(dt-this.getFirstCollision(dt).getTime(),cl)
	 * @effect	If getFirstcollision gives a collision that happens before the given time dt, this method will be 
	 * 			used again with the time parameter equal to the difference of the given time and the time of the collision.
	 * 			| if( 0 < (dt-getFirstCollision(dt).getTime())
	 * 			|	then evolve(dt-this.getFirstCollision(dt).getTime(),cl)
	 */
	public void evolve(double dt, CollisionListener cl) {
		Collision collision = getFirstCollision(dt);
		if(collision==null || Util.fuzzyLessThan(dt, collision.getTime())){
			moveAllSpaceObjects(dt);	
			executeAllPrograms(dt);
		}
		else{
			moveAllSpaceObjects(collision.getTime());
			executeAllPrograms(collision.getTime());
			resolve(collision,cl);
			dt = dt-collision.getTime();
			if(Util.fuzzyLessThan(0,dt)){
				evolve(dt,cl);
			}
		}
	}

	/**
	 * Executes all programs in this world.
	 * 
	 * @param 	time
	 * 			The given time increment.
	 * @effect	If the given time is greater than 1/200 milliseconds, than all ships who use this program are executed and the 
	 * 			program will be executed again with a smaller time increment.
	 * 			| for each spaceObject from getAllSpaceObjects():
	 * 			|	if(spaceObject instanceof Ship && ((Ship)spaceObject).getProgram()!=null) 
	 *			|		then ((Ship)spaceObject).getProgram().execute((Ship) spaceObject);
	 *			| executeAllPrograms(time - 200)
	 * 
	 */
	private void executeAllPrograms(double time) {
		if(Util.fuzzyLessThan(1/200, time)) {
			Set<SpaceObject> sos = new HashSet<SpaceObject>();
			sos.addAll(getAllSpaceObjects());
			for(SpaceObject spaceObject : sos){
				if(spaceObject instanceof Ship && ((Ship)spaceObject).getProgram()!=null){		
					((Ship)spaceObject).getProgram().execute((Ship) spaceObject);
				}
			}
			time = time - 200;
			executeAllPrograms(time);
		} 
	}

	/**
	 * Returns the textual representation of this world.
	 * 
	 * @return 	The textual representation of this world consisting of its height and width.
	 * 			| result = ("World with height "+ getSize().getYCoordinate()+ "and with"+ getSize().getXCoordinate())
	 */
	@Override
	public String toString(){
		return("World with height "+ getSize().getYCoordinate()+ "and with"+ getSize().getXCoordinate());
	}

	public int getNbBulletsFromShip(Ship ship){
		Set<Bullet> bullets = getSpaceObjectsOfType(Bullet.class);
		int count = 0;
		for(Bullet bullet : bullets){
			if(bullet.getSource()==ship){
				count++;
			}
		}
		return count;
	}
}
